﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.ComponentModel.Design;
using System.Windows.Forms;
using System.Data.Objects;
using EFTracingProvider;
using UtfprLibrary.EntityFramework.ComponentModel;
using UtfprLibrary.EntityFramework.Exceptions;

namespace UtfprLibrary.EntityFramework.Windows.Forms {
    public abstract class EntityDataSourceBase : Component , IEntityDataSourceProvider {
        #region IEntityDataSourceProvider

        bool IListSource.ContainsListCollection {
            get { return true; }
        }

        System.Collections.IList IListSource.GetList() {
            try {
                // This method is completely undocumented in MSDN.
                //
                // Instead of returning a list of the lists available (nice and logical),
                // you should return a list with ONE object, which exposes the lists available 
                // as if they were properties of this ONE object. UGH!
                //
                var list = new List<EntitySetTypeDescriptor>();
                list.Add(new EntitySetTypeDescriptor(this.EntityDataSource));

                return list;
            } catch (Exception ex) {
                throw new UtfprEFComponentsException("", ex);
            }
        }

        private ComponentCollection _bindingSources;
        [Category("Data"), Description("A IComponent Collection representing all BindingSources of the Form.")]
        public ComponentCollection BindingSources {
            get {
                try {
                    var host = base.Site.GetService(typeof(IDesignerHost)) as IDesignerHost;
                    if (host != null) {
                        if (host.RootComponent is ContainerControl) {
                            //get the binding sources
                            var bindingSources = (host.RootComponent as ContainerControl)
                                .Container.Components.OfType<BindingSource>();

                            if (_bindingSources == null || _bindingSources.Count != bindingSources.Count()) {
                                var ic = new IComponent[bindingSources.Count()];
                                int i = 0;
                                foreach (var bs in bindingSources) {
                                    ic[i] = bs;
                                    i++;
                                }
                                _bindingSources = new ComponentCollection(ic);
                            }
                        }
                    }

                    return _bindingSources;
                } catch (Exception ex) {
                    throw new UtfprEFComponentsException("", ex);
                }
            }
        }

        IEntityDataSource IEntityDataSourceProvider.EntityDataSource {
            get {  return EntityDataSource; }
        }

        #endregion

        #region IComponentChangeService

        private IComponentChangeService _changeService;

        // This override allows the control to register event handlers for IComponentChangeService events
        // at the time the control is sited, which happens only in design mode.
        public override ISite Site {
            get { return base.Site; }
            set {
                try {
                    // Clear any component change event handlers.
                    clearChangeNotifications();

                    // Set the new Site value.
                    base.Site = value;

                    _changeService = (IComponentChangeService)GetService(typeof(IComponentChangeService));

                    // Register event handlers for component change events.
                    registerChangeNotifications();
                } catch (Exception ex) {
                    throw new UtfprEFComponentsException("", ex);
                }
            }
        }

        private void clearChangeNotifications() {
            try {
                // The m_changeService value is null when not in design mode, 
                // as the IComponentChangeService is only available at design time.	
                _changeService = (IComponentChangeService)GetService(typeof(IComponentChangeService));

                // Clear our the component change events to prepare for re-siting.				
                if (_changeService != null) {
                    _changeService.ComponentAdded -= new ComponentEventHandler(onComponentAdded);
                }
            } catch (Exception ex) {
                throw new UtfprEFComponentsException("", ex);
            }
        }

        private void registerChangeNotifications() {
            try {
                // Register the event handlers for the IComponentChangeService events
                if (_changeService != null) {
                    _changeService.ComponentAdded += new ComponentEventHandler(onComponentAdded);
                }
            } catch (Exception ex) {
                throw new UtfprEFComponentsException("", ex);
            }
        }

        /* This method handles the OnComponentAdded event to display a notification. */
        private void onComponentAdded(object sender, ComponentEventArgs ce) {
            try {
                if (ce.Component is BindingSource) {
                    var bs = (BindingSource)ce.Component;
                    if (this.EntityDataSource != null && bs.DataSource != null) {
                        foreach (var es in this.EntityDataSource.EntitySets) {
                            try {
                                if (es.ElementType == (Type)bs.DataSource) {
                                    bs.DataSource = this;
                                    bs.DataMember = es.Name;
                                }
                            } catch { }
                        }
                    }
                } else if (ce.Component is IEntityDataSourceProvider) {
                    var host = base.Site.GetService(typeof(IDesignerHost)) as IDesignerHost;
                    if (host != null) {
                        if (host.RootComponent is ContainerControl) {
                            var containerControl = host.RootComponent as ContainerControl;
                            if (containerControl.Container.Components.OfType<IEntityDataSourceProvider>().Count() > 1) {
                                throw new ArgumentException("Cannot have multiple datasources.");
                            }

                            var bindingSources = containerControl
                                .Container.Components.OfType<BindingSource>();

                            foreach (var bs in bindingSources) {
                                if (this.EntityDataSource != null && bs.DataSource != null) {
                                    foreach (var es in this.EntityDataSource.EntitySets) {
                                        try {
                                            if (es.ElementType == (Type)bs.DataSource) {
                                                bs.DataSource = this;
                                                bs.DataMember = es.Name;
                                            }
                                        } catch { }
                                    }
                                }
                            }
                        }
                    }
                }
            } catch (Exception ex) {
                throw new UtfprEFComponentsException("", ex);
            }
        }

        #endregion

        #region Constructors

        public EntityDataSourceBase() {
            initializeComponent();
        }

        public EntityDataSourceBase(IContainer container)
            : this() {
            container.Add(this);
        }

        #endregion

        #region Properties

        private IObjectContextTracingProvider _provider;

        public abstract IEntityDataSource EntityDataSource { get; }

        public abstract Type ObjectContextType { get; }

        public abstract bool IsAdvancedTracing { get; }

        #endregion

        #region Methods

        /// <summary>
        /// Provides the connection string used to create an ObjectContext
        /// <para> example: </para>
        /// <para> ... </para> 
        /// <para> var providerCs = new OracleConnectionStringBuilder(); </para>
        /// <para> providerCs.UserId = "xxx"; </para>
        /// <para> providerCs.Password = "xxx"; </para>
        /// <para> providerCs.Server = "192.168.20.25"; </para>
        /// <para> providerCs.Direct = true; </para>
        /// <para> providerCs.Sid = "xxx"; </para>
        /// <para> providerCs.PersistSecurityInfo = true; </para>
        /// <para> var csBuilder = new EntityConnectionStringBuilder(); </para>
        /// <para> csBuilder.Provider = "xxx"; </para>
        /// <para> csBuilder.ProviderConnectionString = providerCs.ToString(); </para>
        /// <para> csBuilder.Metadata = string.Format( </para>
        /// <para> "res://{0}/DataModel1.csdl|res://{0}/DataModel1.ssdl|res://{0}/DataModel1.msl", </para>
        /// <para> typeof(DataModel1Entities).Assembly.FullName); </para>
        /// <para> return csBuilder.ToString(); </para>
        /// </summary>
        /// <returns></returns>
        public abstract string GetConnectionString();

        /// <summary>
        /// Save the changes to the database.
        /// To apply any logic/validation, override this method and call base.SaveChanges() if everything is OK.
        /// </summary>
        public virtual void SaveChanges() {
            try {
                this.EntityDataSource.SaveChanges();
            } catch (Exception ex) {
                throw new UtfprEFComponentsException("", ex);
            }
        }

        public void initializeComponent() {
            try {
                this.EntityDataSource.ObjectContext = getObjectContext();
            } catch (Exception ex) {
                throw new UtfprEFComponentsException("", ex);
            }
        }

        public ObjectContext getObjectContext() {
            try {
                if (IsAdvancedTracing && typeof(IObjectContextTracingProvider).IsAssignableFrom(ObjectContextType)) {
                    _provider = (IObjectContextTracingProvider)Activator.CreateInstance(ObjectContextType, new object[] { GetConnectionString() });

                    EFTracingProviderConfiguration.RegisterProvider();

                    // disable global logging to console
                    EFTracingProviderConfiguration.LogToConsole = false;

                    _provider.Tracing.CommandExecuting += (sender, e) => {
                        Console.WriteLine("Command is executing: {0}", e.ToTraceString());
                    };

                    _provider.Tracing.CommandFinished += (sender, e) => {
                        Console.WriteLine("Command has finished: {0}", e.ToTraceString());
                    };

                    return _provider.Tracing.ObjectContext;
                } else {
                    return (ObjectContext)Activator.CreateInstance(ObjectContextType, new object[] { GetConnectionString() });
                }
            } catch (Exception ex) {
                throw new UtfprEFComponentsException("", ex);
            }
        }
        
        #endregion
    }
}
